package com.qf.service.impl;

import com.alibaba.fastjson.JSON;
import com.qf.entity.Hotal;
import com.qf.entity.SearchParams;
import com.qf.service.ISearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.PutMappingRequest;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.text.DecimalFormat;
import java.util.*;

@Service
public class SearchServiceImpl implements ISearchService {

    @Autowired
    private RestHighLevelClient client;

    private static final String INDEX_NAME = "hotal_index";

    /**
     * 创建索引
     * @return
     */
    @Override
    public boolean createIndex() throws IOException {
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(INDEX_NAME);
        Map<String, Object> map = new HashMap<>();
        map.put("number_of_shards", 1);
        map.put("number_of_replicas", 0);
        createIndexRequest.settings(map);
        CreateIndexResponse response = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
        return response.isAcknowledged();
    }

    /**
     * 判断索引是否存在
     * @return
     * @throws IOException
     */
    @Override
    public boolean isIndex() throws IOException {
        GetIndexRequest getIndexRequest = new GetIndexRequest(INDEX_NAME);
        boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
        return exists;
    }

    /**
     * 删除索引
     * @return
     */
    @Override
    public boolean deleteIndex() throws IOException {
        DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(INDEX_NAME);
        AcknowledgedResponse resp = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
        return resp.isAcknowledged();
    }

    /**
     * 添加映射类型 - 创建表结构
     *
     * PUT /test_index/_mapping/hotal
     * {
     *   "properties": {
     *     "hotalName":{
     *       "type": "text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "hotalInfo":{
     *       "type": "text",
     *       "analyzer": "ik_max_word"
     *     },
     *     "hotalImage":{
     *       "type": "keyword",
     *       "index": false
     *     },
     *     "price":{
     *       "type": "scaled_float",
     *       "scaling_factor": 100
     *     },
     *     "openTime":{
     *       "type": "date",
     *       "format": "yyyy-MM-dd"
     *     },
     *     "location":{
     *       "type": "geo_point"
     *     },
     *     "cityName":{
     *       "type": "keyword"
     *     }
     *   }
     * }
     *
     *
     * @return
     */
    @Override
    public boolean addMappings() throws IOException {

        PutMappingRequest putMappingRequest = new PutMappingRequest(INDEX_NAME);

        //构建映射关系
        XContentBuilder builder = JsonXContent.contentBuilder();
        builder
                .startObject()
                .startObject("properties")

                .startObject("hotalName")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("hotalImage")
                .field("type", "keyword")
                .field("index", "false")
                .endObject()

                .startObject("type")
                .field("type", "integer")
                .endObject()

                .startObject("hotalInfo")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("keyword")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("location")
                .field("type", "geo_point")
                .endObject()

                .startObject("star")
                .field("type", "integer")
                .endObject()

                .startObject("brand")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("address")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("openTime")
                .field("type", "date")
                .field("format", "yyyy-MM-dd")
                .endObject()

                .startObject("cityname")
                .field("type", "keyword")
                .endObject()

                .startObject("regid")
                .field("type", "text")
                .field("analyzer", "ik_max_word")
                .endObject()

                .startObject("price")
                .field("type", "double")
                .endObject()

                .startObject("roomNumber")
                .field("type", "integer")
                .endObject()

                .startObject("djl")
                .field("type", "integer")
                .endObject()

                .startObject("gzl")
                .field("type", "integer")
                .endObject()

                .endObject()
                .endObject();


        putMappingRequest.source(builder);

        AcknowledgedResponse response = client.indices().putMapping(putMappingRequest, RequestOptions.DEFAULT);
        return response.isAcknowledged();
    }

    /**
     * 添加文档
     * @param hotal
     * @return
     */
    @Override
    public boolean addDocument(Hotal hotal) throws IOException {

        IndexRequest indexRequest = new IndexRequest(INDEX_NAME, "_doc", hotal.getId() + "");
        //使用FastJSON转换对象 -> json
        //String json = "{'hotalName':xxxx, 'hotalInfo':xxxx, 'xxxx': xxxx}";
        String json = JSON.toJSONString(hotal);
        System.out.println("酒店对象转换的json：" + json);

        indexRequest.source(json, XContentType.JSON);
        IndexResponse response = client.index(indexRequest, RequestOptions.DEFAULT);
        return response.getShardInfo().getSuccessful() == 1;
    }

    /**
     * 通过查询条件，执行相应的查询
     * @param searchParams
     * @return
     * @throws IOException
     */
    @Override
    public List<Hotal> query(SearchParams searchParams) throws IOException {

        //构建查询构建器

        //城市查询
        TermQueryBuilder cityQuery = QueryBuilders.termQuery("cityname", searchParams.getCityName());

        //通过关键词匹配多个字段
        QueryBuilder keywordQuery = null;
        if (StringUtils.isNotEmpty(searchParams.getKeyword())) {
            //用户关键字不为空
            keywordQuery = QueryBuilders
                    .multiMatchQuery(searchParams.getKeyword())
                    .field("hotalName").boost(2)
                    .field("brand").boost(2)
                    .field("regid")
                    .field("keyword")
                    .field("hotalInfo");
        } else {
            keywordQuery = cityQuery;
        }


        //通过价格限制条件查询
        RangeQueryBuilder priceQuery = QueryBuilders.rangeQuery("price")
                .gte(searchParams.getMinPirce() != null ? searchParams.getMinPirce().doubleValue() : 0)
                .lte(searchParams.getMaxPirce() != null ? searchParams.getMaxPirce().doubleValue() : Integer.MAX_VALUE);

        //bool查询将以上两个查询整合
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                .must(keywordQuery)
                .must(priceQuery);

        //降级查询 - 符合这个条件的文档，评分会降低
        //bool对城市查询取非
        BoolQueryBuilder boolQuery2 = QueryBuilders.boolQuery().mustNot(cityQuery);

        //使用boosting查询将以上两个bool查询组合起来
        BoostingQueryBuilder execQuery = QueryBuilders.boostingQuery(
                boolQuery,
                boolQuery2
        ).negativeBoost(0.2f);

        //创建排序器 1-智能排序 2-价格排序 3-距离排序
        SortBuilder sortBuilder = null;
        switch (searchParams.getSortType()){
            case 1:
                break;
            case 2:
                //价格排序，默认升序
                sortBuilder = SortBuilders.fieldSort("price").order(SortOrder.ASC);
                break;
            case 3:
                //距离排序
                sortBuilder = SortBuilders
                        .geoDistanceSort("location", searchParams.getLat(), searchParams.getLon())
                        .unit(DistanceUnit.KILOMETERS)
                        .order(SortOrder.ASC);
                break;
        }


        //执行查询
        SearchResponse response = query(execQuery, sortBuilder);

        //封装查询结果
        
        //获得查询总数
        long totalHits = response.getHits().totalHits;
        System.out.println("查询的结果总数：" + totalHits);

        //可以再进行一次模糊查询
//        if(totalHits == 0){
//            //进行模糊查询
//            QueryBuilders.fuzzyQuery()
//        }
        
        //获得查询结果集
        List<Hotal> hotals = new ArrayList<>();

        SearchHit[] hits = response.getHits().getHits();
        Arrays.stream(hits).forEach(hit -> {
            //一个hit就代表一个文档，也就是一个酒店
            System.out.println("酒店信息：" + hit.getSourceAsString());
            //获得酒店的json
            String hotalJson = hit.getSourceAsString();
            //将酒店json转换成Hotal对象
            Hotal hotal = JSON.parseObject(hotalJson, Hotal.class);

            //获得距离信息
            if (searchParams.getSortType() == 3){
                //按照距离排序
                //获取距离信息
                Double juli = (Double) hit.getSortValues()[0];
                System.out.println("距离：" + juli);
                DecimalFormat decimalFormat = new DecimalFormat("#.00");
                hotal.setJuli(decimalFormat.format(juli) + "km");
            }
            System.out.println("酒店对象：" + hotal);

            hotals.add(hotal);
        });

        return hotals;
    }

    /**
     * 查询基本业务
     * @return
     */
    private SearchResponse query(QueryBuilder queryBuilder, SortBuilder sortBuilder) throws IOException {

        SearchRequest searchRequest = new SearchRequest(INDEX_NAME);

        SearchSourceBuilder searchBuilder = new SearchSourceBuilder()
                .query(queryBuilder);

        //如果排序构建器不等于空，则设置排序构建器
        if (sortBuilder != null) {
            searchBuilder.sort(sortBuilder);
        }

        //设置查询构建器
        searchRequest.source(searchBuilder);

        //执行查询
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
        return response;
    }
}
